/**
 * 
 */
package pacMan;

/**
 * Class: Actor
 * 
 * Base class for animated game characters.
 * 
 * @author 		Tomas Rivera
 * @version 	$Id$
 *
 */
public abstract class Actor extends ConsumableObject {
	
	//////////////////////////////////////////////////////////
	// Constructor methods
	//////////////////////////////////////////////////////////

	/**
	 * Creates an Actor object, which is an animated game character that can be moved on-screen.
	 * 
	 * @param c
	 * @param w
	 * @param pValue
	 * @param objectLocationTracker
	 */
	public Actor(Coordinate c, WindowParameters w, int pValue, ObjectLocationTracker objectLocationTracker) {
		super(c, w, pValue);
		this.objectLocationTracker = objectLocationTracker;
		homeLocation = null;
		
		framesToFreeze = 0;
		hideWhileFrozen = false;
		movesPerFrameRemainder = 0.0;
	}
	
	//////////////////////////////////////////////////////////
	// Abstract methods
	//////////////////////////////////////////////////////////
	
	/**
	 * Function will handle moving character a set distance on screen. Will use ObjectLocationTracker class
	 * to determine what movements are legal, and will handle checking for player/enemy collisions.
	 * 
	 * @param distance
	 */
	public abstract GameStateManager.GAME_EVENT moveActor(GameStateManager.MOVE_BEHAVIOR ghostGlobalState);
	
	/**
	 * Increments point value managed by ScoreTracker class.
	 * 
	 * @param pointValue
	 * @param isEnemyActor
	 */
	public abstract void addToScore(int pointValue, boolean isEnemyActor);

	
	//////////////////////////////////////////////////////////
	// Public / Protected methods
	//////////////////////////////////////////////////////////
	
	/**
	 * This is used to deal with non-integer result that we get when we divide the
	 * movement speed by the number of frames per second. Each time we call this we
	 * add the value given to our locally stored value, and if we exceed one we return
	 * the integer value '1' and subtract that amount from the local total.
	 * 
	 * @param r
	 * @return
	 */
	public int incrementMovesPerFrameRemainder(double r) {
		movesPerFrameRemainder += r;
		if(movesPerFrameRemainder >= 1.0) {
			movesPerFrameRemainder -= 1.0;
			return 1;
		}
		
		return 0;
	}
	
	/**
	 * Picks up and moves Actor back to the location defined by setHomeLocation().
	 * 
	 */
	public void returnActorToHomeLocation() {
		if(homeLocation != null) {
			homeLocation.update(getWindowParams().getWindowWidth(), getWindowParams().getWindowHeight());
			getObjectLocation().changePosition(homeLocation.getX(), homeLocation.getY());
		}
	}
	
	/**
	 * Clones the coordinate object passed and saves as the home location for the actor.
	 * 
	 * @param co
	 */
	public void setHomeLocation(Coordinate co) {
		this.homeLocation = co.cloneCoordinateObject();
	}
	
	/**
	 * Gets Actor current movement speed in pixels/sec.
	 * 
	 * @return
	 */
	public double getPixelSpeed() { return pixelSpeed; }
	
	/**
	 * Sets Actor current movement speed in pixels/sec.
	 * 
	 * @param speed
	 */
	public void setPixelSpeed(double speed) { pixelSpeed = speed; }
	
	/**
	 * Gets Actor current direction of movement.
	 * 
	 * @return
	 */
	public CardinalDirection getDirection() { return direction; }
	
	/**
	 * Sets movement direction to the opposite of what it currently is.
	 * 
	 */
	public void reverseDirection() {
		setDirection(getDirection().opposite());
	}
	
	/**
	 * Changes Actor direction of movement.
	 * 
	 * @param direction
	 */
	protected void setDirection(CardinalDirection direction) { this.direction = direction; }
	
	/**
	 * 
	 * @param pixels
	 */
	protected void moveActor(int pixels) {
		moveActor(direction, pixels);
	}
	
	/**
	 * Moves Actor specified number of pixels in specified direction.
	 * 
	 * @param direction
	 * @param pixels
	 */
	protected void moveActor(CardinalDirection direction, int pixels) {
		switch(direction) {
			case NORTH:
				this.direction = direction;
				getObjectLocation().moveUp(pixels);
				break;
				
			case SOUTH:
				this.direction = direction;
				getObjectLocation().moveDown(pixels);
				break;
				
			case EAST:
				this.direction = direction;
				getObjectLocation().moveRight(pixels);
				break;
				
			case WEST:
				this.direction = direction;
				getObjectLocation().moveLeft(pixels);
				break;
				
			default:
				assert false;
				break;
		}
		
		// Handle wrapping around to other side of screen
		int diameter = getWindowParams().getPixelScale() * 150 / 100;
		if(getObjectLocation().getX() <= (0 - (diameter / 2))) {
			getObjectLocation().changePosition(getWindowParams().getWindowWidth() + (diameter / 2), getObjectLocation().getY());
		} else if(getObjectLocation().getX() >= (getWindowParams().getWindowWidth() + (diameter / 2))) {
			getObjectLocation().changePosition(0 - (diameter / 2), getObjectLocation().getY());
		}
		
		//System.out.printf("GridX=%d GridY=%d x=%d y=%d%n", this.getGridLocationX(), getGridLocationY(), this.getObjectLocation().getX(), this.getObjectLocation().getY());
	}
	
	/**
	 * Check if this Actor's movement is currently blocked.
	 * @return
	 */
	public boolean isFrozen() { return (framesToFreeze != 0); }
	
	/**
	 * Set that this Actor is not allowed to move for the specified number of frames.
	 * 
	 * @param frameCount
	 */
	public void setFrozen(int frameCount) { this.framesToFreeze = frameCount; }
	
	/**
	 * Decrement the count of frames that the Actor is not allowed to move.
	 */
	public void decrementFrozen() {
		if(framesToFreeze != 0) {
			framesToFreeze--;
		}
		
		if(framesToFreeze == 0) {
			hideWhileFrozen = false;
		}
	}
	
	/**
	 * Flag that the Actor should not be drawn as long as it's frozen count is not zero.
	 * 
	 */
	public void setHiddenWhileFrozen() { hideWhileFrozen = true; }
	
	/**
	 * Check if this Actor should be drawn on the screen.
	 * 
	 * @return
	 */
	public boolean hideObject() {
		return (hideWhileFrozen && framesToFreeze != 0);
	}

	//////////////////////////////////////////////////////////
	// Private member variables
	//////////////////////////////////////////////////////////

	private CardinalDirection direction;
	protected ObjectLocationTracker objectLocationTracker;
	private double pixelSpeed;
	private Coordinate homeLocation;
	private boolean hideWhileFrozen;
	private int framesToFreeze;
	private double movesPerFrameRemainder;
}
